package com.cfw.exception;

import java.util.Arrays;
import java.util.Map;
import java.util.Properties;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.apache.commons.lang3.StringUtils;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.handler.SimpleMappingExceptionResolver;
import org.springframework.web.servlet.view.json.MappingJacksonJsonView;

/**
 * @author Administrator
 *
 */
public class CustomMappingExceptionResolver extends SimpleMappingExceptionResolver {
	public static final String ERR_ATT_CODE = "code"; // 모델에 포함될 오류코드 애트리뷰트 명
	public static final String ERR_ATT_MSG = "message"; // 모델에 포함될 오류메세지 애트리뷰트 명
	
	private static final String PROP_RESOURCE_PATH = "/properties/message.xml"; // 오류메세지가 포함된 프로퍼티 리소스명
	private String defaultCode;  
	private String defaultMessage;  
	private Properties exceptionCodeMappings;
	private String[] enforceJson;
	private String[] skipCodes;
	private Map<String, Map<String, String>> codeViewMappings;
	
	public void setDefaultCode(final String defaultCode) {
		this.defaultCode = defaultCode;
	}
	
	public String getDefaultCode() {
		return this.defaultCode;
	}

	public void setDefaultMessage(final String defaultMessage)	{
		this.defaultMessage = defaultMessage;
	}
	
	public String getDefaultMessage() {
		return this.defaultMessage;
	}
	
	public void setExceptionCodeMappings(final Properties exceptionCodeMappings) {
		this.exceptionCodeMappings = exceptionCodeMappings;
	}
	
	public Properties getExceptionCodeMappings() {
		return this.exceptionCodeMappings;
	}
	
	public void setCodeViewMappings(final Map<String, Map<String, String>> codeViewMappings) {
		this.codeViewMappings = codeViewMappings;
	}
	
	public Map<String, Map<String, String>> getCodeViewMappings() {
		return this.codeViewMappings;
	}
	
	public void setEnforceJson(final String[] enforceJson) {
		this.enforceJson = enforceJson;
	}
	
	public String[] getEnforceJson() {
		return this.enforceJson;
	}

	public void setSkipCodes(final String[] skipCodes) {
		this.skipCodes = skipCodes;
	}
	
	public String[] getSkipCodes() {
		return this.skipCodes;
	}
	
	private Properties errors;
	
	{
		init();
	}
	
		private boolean isAjax(HttpServletRequest request) {
			final String path = request.getRequestURI().substring(request.getContextPath().length());
			
			if (enforceJson != null) {
				logger.debug("*** path:" + path + ",enforceJson:" + Arrays.asList(enforceJson));
			}
			
	        return "XMLHttpRequest".equalsIgnoreCase(request.getHeader("X-Requested-With")) || (
	        		enforceJson != null && Arrays.asList(enforceJson).contains(path)
	        		);
	    }
		
	/**
	 * 초기화 수행 - 오류 메세지를 errors 객체에 로딩 작업 수행
	 */
	public void init() {
		logger.debug("***** 오류 메세지 초기화 작업 시작 *****");
		
		try {
			errors = new Properties();
			errors.loadFromXML(getClass().getResourceAsStream(PROP_RESOURCE_PATH));
		} catch (Exception e) {
			logger.error("***** 오류 메세지 초기화 작업 중 오류");
		}
	}

		/**
		 * Exception의 code attribute로 code 반환
		 * @param ex
		 * @return
		 */
		private String getCode(final Exception ex) {
			String result =	this.getDefaultCode();
			
			if (ex instanceof BizException) {
				final String code =	((BizException)ex).getCode();
	
	            logger.debug("***** code:" + code + " *****");
				
				if (StringUtils.isNotEmpty(code)) {
					result = code;
				}
			}

			return result;
		}

		/**
		 * code로 메세지 반환
		 * @param code
		 * @return
		 */
		private String getMessage(final String code) {
            String msg = this.getDefaultMessage();
            
			if (StringUtils.isNotEmpty(code) && !this.getDefaultCode().equals(code) && this.errors.containsKey(code)) {
				msg = this.errors.getProperty(code);
			}
    			
            logger.debug("***** err code:[" + code + "], err message:[" + msg + "] *****");
            
    		return msg;
		}
	
	/**
	 * json type으로 반환해야 할 경우, ModelAndView를 생성하여 처리, json이 아닌경우는 Super에서 처리
	 * (non-Javadoc)
	 * @see org.springframework.web.servlet.handler.SimpleMappingExceptionResolver#doResolveException(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse, java.lang.Object, java.lang.Exception)
	 */
	@Override
	protected ModelAndView doResolveException(
            HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) {
		
		if (!isAjax(request)) { return super.doResolveException(request, response, handler, ex); }
		
		String code = getCode(ex);

		String exClassName = ex.getClass().getName();
		if (exceptionCodeMappings != null && exceptionCodeMappings.containsKey(exClassName)) {
			code = exceptionCodeMappings.getProperty(exClassName);
		}
		
		if (skipCodes != null && Arrays.asList(skipCodes).contains(code)) {
			logger.debug("!!! skipCodes match - code:" + code);
			return null;
		}
		
		final String msg = getMessage(code);
		
		if (codeViewMappings != null && codeViewMappings.containsKey(exClassName)) {
			String uri = request.getRequestURI();
			Map<String, String> map = codeViewMappings.get(exClassName);
			String exp = map.get("uriPattern");
			logger.debug("!!! codeViewMappgin match - map, uri, exp:" + map + "," + uri + "," + exp);
			if (StringUtils.isNotEmpty(uri) && StringUtils.isNotEmpty(exp) && uri.matches(exp)) {
				if (map.containsKey("internalResourceView")) {
					ModelAndView mnv = new ModelAndView(map.get("internalResourceView"));
					return mnv;
				}
			}
		}
		
        response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        
        ModelAndView mnv = new ModelAndView(new MappingJacksonJsonView());
		mnv.addObject(ERR_ATT_CODE, code);
		mnv.addObject(ERR_ATT_MSG, msg);
        
        return mnv;
	}
	
	/**
	 * getModelAndView에서는 viewName이 String인 경우 만 처리 가능하여, 
	 * json 뷰는 doResolveExcepton에서 Override 처리해야 한다.
	 * @see org.springframework.web.servlet.handler.SimpleMappingExceptionResolver#getModelAndView(java.lang.String, java.lang.Exception)
	 */
	@Override
	protected ModelAndView getModelAndView(String viewName, Exception ex) {
		final ModelAndView mnv = super.getModelAndView(viewName, ex);
		
		final String code =	getCode(ex);
		final String msg = getMessage(code);
		
		mnv.addObject(ERR_ATT_CODE, code);
		mnv.addObject(ERR_ATT_MSG, 	msg);
		
		return mnv;
	}
}
